# -*- coding: utf-8 -*-
# =============================================================================
#     FileName:
#         Desc:
#       Author:
#        Email:
#     HomePage:
#      Version:
#   LastChange:
#      History:
# =============================================================================
import os
import time
import traceback

from utils.log_helper import LogHelper
from utils.mysql_server import MySQLServer
from utils.source_server import SourceServer
from utils.target_server import TargetServer

logger = LogHelper.get_logger()


class MyArchiveType(object):
    MYSQL_LOOP_ARCHIVE = 1
    MYSQL_LOOP_DELETE = 2
    MYSQL_LOOP_COPY = 3


class MyArchiver(object):
    def __init__(self, source_mysql_server: MySQLServer,
                 source_table_name: str,
                 data_condition: str,
                 batch_scan_rows: int,
                 batch_sleep_seconds,
                 batch_insert_rows=100,
                 max_query_seconds=100,
                 max_primary_key="AUTO",
                 min_primary_key="AUTO",
                 task_type=MyArchiveType.MYSQL_LOOP_ARCHIVE,
                 target_mysql_server=None,
                 target_table_name=None,
                 etl_server=None,
                 etl_table_name=None,
                 is_dry_run=True):
        self.source_server = SourceServer(
            mysql_server=source_mysql_server,
            table_name=source_table_name,
            data_condition=data_condition,
            max_query_seconds=max_query_seconds,
            max_key_value=max_primary_key,
            min_key_value=min_primary_key
        )
        if (target_mysql_server is not None) and (target_table_name is not None):
            self.target_server = TargetServer(
                mysql_server=target_mysql_server,
                table_name=target_table_name,
                batch_insert_rows=batch_insert_rows
            )
        else:
            self.target_server = None
        if etl_server is not None and etl_table_name is not None:
            self.etl_source_server = SourceServer(
                mysql_server=etl_server,
                table_name=etl_table_name,
                data_condition=data_condition,
                max_query_seconds=max_query_seconds,
                max_key_value=max_primary_key,
                min_key_value=min_primary_key
            )
        else:
            self.etl_source_server = None
        self.etl_table_name = etl_table_name
        self.batch_scan_rows = batch_scan_rows
        self.batch_sleep_seconds = batch_sleep_seconds
        self.batch_insert_rows = batch_insert_rows
        self.task_type = task_type
        self.is_dry_run = is_dry_run

    @classmethod
    def print_spilt_line(cls):
        logger.info("*" * 70)

    @classmethod
    def has_stop_file(cls):
        working_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        script_folder = os.path.join(working_folder, "scripts")
        if not os.path.exists(script_folder):
            os.mkdir(script_folder)
        stop_script_file = os.path.join(script_folder, "stop.txt")
        logger.info("检测停止文件{}是否存在".format(stop_script_file))
        if os.path.exists(stop_script_file):
            logger.info("检查到停止文件,准备退出...")
            return True
        else:
            return False

    def sleep_with_affect_rows(self, total_affect_rows):
        time.sleep(round(self.batch_sleep_seconds * total_affect_rows / self.batch_insert_rows, 3))

    def archive_data_with_keys(self, key_items):
        logger.info("=" * 30)
        if self.task_type == MyArchiveType.MYSQL_LOOP_ARCHIVE \
                or self.task_type == MyArchiveType.MYSQL_LOOP_COPY:
            row_items = self.source_server.get_data_by_keys(key_items=key_items)
            # key_items = self.source_server.get_new_key_items(key_items=key_items)
            self.target_server.delete_data_by_keys(key_items=key_items)
            self.target_server.insert_row_items(row_items=row_items)
            if not self.target_server.check_data_by_keys(key_items=key_items):
                return False
        if self.task_type == MyArchiveType.MYSQL_LOOP_ARCHIVE \
                or self.task_type == MyArchiveType.MYSQL_LOOP_DELETE:
            self.source_server.delete_data_by_keys(key_items=key_items)
        self.sleep_with_affect_rows(total_affect_rows=len(key_items))

    def split_batch_groups(self, key_items):
        """
        将扫描到的KEY进行分组处理
        :param key_items: 要处理的KEY列表
        :return: 分组后的组列表
        如果将[1,2,3,4,5]按照每批次3条进行分组，返回[[1,2,3],[4,5]]
        """
        batch_group = list()
        batch_groups = list()
        for key_item in key_items:
            batch_group.append(key_item)
            if len(batch_group) == self.batch_insert_rows:
                batch_groups.append(batch_group)
                batch_group = []
        if len(batch_group) > 0:
            batch_groups.append(batch_group)
        return batch_groups

    def archive_range_data(self, current_key_value, next_key_value, max_key_value):
        key_items = self.get_range_keys(
            current_key_value=current_key_value,
            next_key_value=next_key_value,
            max_key_value=max_key_value
        )
        logger.info("找到满足条件记录{}条".format(len(key_items)))
        if len(key_items) == 0:
            return True
        if self.is_dry_run:
            logger.info("未真实执行，未生产SQL文件。")
            return True
        batch_groups = self.split_batch_groups(key_items=key_items)
        for batch_keys in batch_groups:
            self.archive_data_with_keys(key_items=batch_keys)
        return True

    def check_and_create_target_table(self):
        if self.target_server.is_table_exist():
            return True
        source_table_script = self.source_server.get_create_table_script()
        target_table_script = str(source_table_script).replace(
            "CREATE TABLE `{}`".format(self.source_server.table_name),
            "CREATE TABLE `{}`.`{}`".format(self.target_server.database_name, self.target_server.table_name),
        )
        self.target_server.mysql_server.mysql_exec(sql_script=target_table_script)

    def check_config_with_delete(self):
        try:
            if str(self.source_server.data_condition).strip() == "":
                logger.warning("迁移条件不能为空,退出...")
                return False
            source_columns = self.source_server.get_table_columns()
            column_count = len(source_columns)
            primary_key_count = 0
            primary_key_name = ""
            primary_key_type = ""
            for column_id in range(column_count):
                source_column_name = source_columns[column_id]["Field"]
                source_column_key = source_columns[column_id]["Key"]
                source_column_type = source_columns[column_id]["Type"]
                if source_column_key.lower() == 'pri':
                    primary_key_count += 1
                    primary_key_name = source_column_name
                    if 'int(' in str(source_column_type).lower() \
                            or 'bigint(' in str(source_column_type).lower():
                        primary_key_type = 'INT'
                    elif 'varchar(' in str(source_column_type).lower():
                        primary_key_type = 'CHAR'
            if primary_key_type == "":
                logger.warning("主键不为int/bigint/varchar三种类型中的一种，不满足迁移条件")
                return False
            if primary_key_count == 0:
                logger.warning("未找到主键，不瞒足迁移条件")
                return False
            if primary_key_count > 1:
                logger.warning("要迁移的表使用复合主键，不满足迁移条件")
                return False
            self.init_primary_key(primary_key_name=primary_key_name, primary_key_type=primary_key_type)
            return True
        except Exception as ex:
            logger.warning("执行出现异常，异常为{0},{1}".format(
                str(ex), traceback.format_exc()))
            return False

    def check_config_with_archive(self):
        try:
            if str(self.source_server.data_condition).strip() == "":
                logger.warning("迁移条件不能为空,退出...")
                return False
            if self.target_server is None:
                logger.warning("未配置目标服务器,退出...")
                return False
            tss = self.target_server.mysql_server
            sss = self.source_server.mysql_server
            if tss.mysql_host == sss.mysql_host \
                    and tss.mysql_port == sss.mysql_port \
                    and tss.database_name == sss.database_name \
                    and self.source_server.table_name == self.target_server.table_name:
                logger.warning("源表和目标表相同，配置有误，请检查")
                return
            self.check_and_create_target_table()
            source_columns = self.source_server.get_table_columns()
            target_columns = self.target_server.get_table_columns()
            if len(source_columns) != len(target_columns):
                logger.warning("源表和目标表的列数量不匹配，不满足迁移条件")
                return False
            column_count = len(source_columns)
            primary_key_count = 0
            primary_key_name = ""
            primary_key_type = ""
            for column_id in range(column_count):
                source_column_name = source_columns[column_id]["Field"]
                source_column_key = source_columns[column_id]["Key"]
                source_column_type = source_columns[column_id]["Type"]
                target_column_name = target_columns[column_id]["Field"]
                target_column_key = target_columns[column_id]["Key"]
                target_column_type = target_columns[column_id]["Type"]
                if source_column_name.lower() != target_column_name.lower() \
                        or target_column_key.lower() != target_column_key.lower() \
                        or source_column_type.lower() != target_column_type.lower():
                    logger.warning("源表和目标表的列不匹配，不满足迁移条件")
                    return False
                if source_column_key.lower() == 'pri':
                    primary_key_count += 1
                    primary_key_name = source_column_name
                    if 'int(' in str(source_column_type).lower() \
                            or 'bigint(' in str(source_column_type).lower():
                        primary_key_type = 'INT'
                    elif 'varchar(' in str(source_column_type).lower():
                        primary_key_type = 'CHAR'
            if primary_key_type == "":
                logger.warning("主键不为int/bigint/varchar三种类型中的一种，不满足迁移条件")
                return False
            if primary_key_count == 0:
                logger.warning("未找到主键，不瞒足迁移条件")
                return False
            if primary_key_count > 1:
                logger.warning("要迁移的表使用复合主键，不满足迁移条件")
                return False
            self.init_primary_key(primary_key_name=primary_key_name, primary_key_type=primary_key_type)
            return True
        except Exception as ex:
            logger.warning("执行出现异常，异常为{0},{1}".format(
                str(ex), traceback.format_exc()))
            return False

    def check_config(self):
        if self.task_type == MyArchiveType.MYSQL_LOOP_ARCHIVE or self.task_type == MyArchiveType.MYSQL_LOOP_COPY:
            return self.check_config_with_archive()
        elif self.task_type == MyArchiveType.MYSQL_LOOP_DELETE:
            return self.check_config_with_delete()
        else:
            logger.warning("未知的归档类型")
            return False

    def init_primary_key(self, primary_key_name, primary_key_type):
        self.source_server.primary_key_name = primary_key_name
        self.source_server.primary_key_type = primary_key_type
        if self.target_server is not None:
            self.target_server.primary_key_name = primary_key_name
            self.target_server.primary_key_type = primary_key_type
        if self.etl_source_server is not None:
            self.etl_source_server.primary_key_name = primary_key_name
            self.etl_source_server.primary_key_type = primary_key_type

    def get_next_loop_key(self, current_key_value, batch_scan_rows):
        if self.etl_source_server is not None:
            return self.etl_source_server.get_next_loop_key(
                current_key_value=current_key_value,
                batch_scan_rows=batch_scan_rows
            )
        else:
            return self.source_server.get_next_loop_key(
                current_key_value=current_key_value,
                batch_scan_rows=batch_scan_rows
            )

    def get_range_keys(self, current_key_value, next_key_value, max_key_value):
        if self.etl_source_server is None:
            range_keys = self.source_server.get_key_items(
                current_key_value=current_key_value,
                next_key_value=next_key_value,
                max_key_value=max_key_value
            )
        else:
            range_keys = self.etl_source_server.get_key_items(
                current_key_value=current_key_value,
                next_key_value=next_key_value,
                max_key_value=max_key_value
            )
        return range_keys

    def get_loop_key_range(self):
        if self.etl_source_server is None:
            range_keys = self.source_server.get_loop_key_range()
        else:
            range_keys = self.etl_source_server.get_loop_key_range()
        return range_keys

    def loop_archive_data(self):
        self.print_spilt_line()
        logger.info("尝试获取归档数据区间")
        if self.check_config():
            logger.info("检查配置通过")
        else:
            logger.info("检查配置文件出现异常,退出...")
            return False
        max_key_value, min_key_value = self.get_loop_key_range()
        if max_key_value is None or min_key_value is None:
            logger.info("未找到满足条件的归档期间,退出...")
            return True
        logger.info("归档期间为：{}至{}".format(min_key_value, max_key_value))
        self.print_spilt_line()
        current_key_value = min_key_value
        while current_key_value <= max_key_value:
            logger.info("""归档数据库:{},归档数据表:{}""".format(
                self.source_server.database_name,
                self.source_server.table_name,

            ))
            if self.task_type == MyArchiveType.MYSQL_LOOP_ARCHIVE:
                logger.info("""目标数据库:{},目标表:{}""".format(
                    self.target_server.database_name,
                    self.target_server.table_name
                ))
            if self.etl_source_server is not None:
                logger.info("ETL数据库:{},ETL数据表:{}".format(
                    self.etl_source_server.database_name,
                    self.etl_source_server.table_name))
                logger.info("归档条件: {}".format(self.etl_source_server.data_condition))
            else:
                logger.info("归档条件: {}".format(self.source_server.data_condition))
            if self.has_stop_file():
                return True
            next_key_value = self.get_next_loop_key(
                current_key_value=current_key_value,
                batch_scan_rows=self.batch_scan_rows
            )
            if next_key_value is None:
                next_key_value = max_key_value
            elif next_key_value >= max_key_value:
                next_key_value = max_key_value
            logger.info("""归档数据范围：{}至{},本轮循环处理：{}至{}""".format(
                min_key_value, max_key_value,
                current_key_value, next_key_value
            ))
            if self.archive_range_data(
                    current_key_value=current_key_value,
                    next_key_value=next_key_value,
                    max_key_value=max_key_value):
                logger.info("本轮归档成功完成")
                current_key_value = next_key_value
                self.print_spilt_line()
            else:
                logger.warning("本轮归档出现异常,退出...")
                self.print_spilt_line()
                return False
            if next_key_value == max_key_value:
                break
        logger.info("归档操作执行完成,退出...")
        self.print_spilt_line()
        return True
